using System;
using System.Web;
using System.Web.Caching;
using Dimok;
using ConfigApp = Dimok.Config.application;
using Dimok.Data.Database;
using Dimok.Exceptions;

////////////////////////////////////////////////////////////////////////////////////////////////////
// namespace: Dimok.Concurrency
//
// summary:	.
////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Dimok.Concurrency{
	public class ConcurrencyException : ApplicationException{

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Constructor. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	Identifier for the appl. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public ConcurrencyException(int applID):base(BuildMessage(applID)){}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Builds a message. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	Identifier for the appl. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string BuildMessage(int applID){
			ConcurrencyTag tag = Locker.GetTag(applID);
			return tag == null ? "Unknown concurrency error with applicant "+applID :
				"Applicant "+applID+" has possibly been changed by "+tag.UserName+"."+Environment.NewLine+
				"Close this window and open it again if you want to edit this applicant."
				+Environment.NewLine+
				"TimeTag:"+tag.TimeTag+Environment.NewLine+
				"PageTag:"+Locker.PageSessionID
				;
		}
	}
	public class ConcurrencyTag{
		public string PageSessionID;
		public string TimeTag;
		public string UserName;
		public string PageName;
		public DateTime Expiration;

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Default constructor. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public ConcurrencyTag(){}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Constructor. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="pageSessionID">	Identifier for the page session. </param>
		/// <param name="timeTag">				The time tag. </param>
		/// <param name="userName">				Name of the user. </param>
		/// <param name="pageName">				Name of the page. </param>
		/// <param name="expiration">			Date/Time of the expiration. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public ConcurrencyTag(string pageSessionID,string timeTag,string userName,string pageName,DateTime expiration){
			PageSessionID = pageSessionID;
			TimeTag = timeTag;
			UserName = userName;
			PageName = pageName;
			Expiration = expiration;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Appends a time tag. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="timeTag">	The time tag. </param>
		///
		/// <returns>	. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string AppendTimeTag(string timeTag) {
			return
@"
<SCRIPT>
function TimeTagTop(){ 
	return (top.dialogArguments||{}).opener || top;
}
if( !TimeTagTop().WS_TIME_TAG ){" + (ConfigApp.LockDebug ? "debugger;" : "") + @"
	try{TimeTagTop().WS_TIME_TAG = '" + Locker.TIME_TAG_NAME + "," + timeTag + @"';}catch(e){}
}
function timeTagName(){return timeTag().split(',')[0]||''}
function timeTagValue(){return timeTag().split(',')[1]||''}
function timeTag(){return TimeTagTop().WS_TIME_TAG||''}
</SCRIPT>
";
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	
		/// Returns a <see cref="T:System.String" /> that represents the current <see cref="T:
		/// System.Object" />. 
		/// </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <returns>	
		/// A <see cref="T:System.String" /> that represents the current <see cref="T:System.Object" />. 
		/// </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public override string ToString(){
			string[] s= new string[]{"PageSessionID:["+PageSessionID+"]","TimeTag:["+TimeTag+"]","UserName:["+UserName+"]","PageName:["+PageName+"]","Expiration in:["+((TimeSpan)Expiration.Subtract(DateTime.Now)).ToString()+"]"};
			return string.Join("<br>",s);
		}
	}
	/// <summary>
	/// Manage concurent access to Applicants from different users from different pages
	/// </summary>
	public class Locker{
		public const string TIME_TAG_NAME = "PAGE_DATETIME";
		public const string NO_LOCK_PARAM = "NoLock";

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a value indicating whether the no is locked. </summary>
		///
		/// <value>	true if no lock, false if not. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static bool NoLock { get { return (HttpContext.Current.Request[NO_LOCK_PARAM]+"") == "1"; } }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the no lock parameter. </summary>
		///
		/// <value>	The no lock parameter. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string NoLockParam { get { return NO_LOCK_PARAM+"=1"; } }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the time tag. </summary>
		///
		/// <value>	The time tag. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string TimeTag{get{return DateTime.Now.ToString("yyMMdd.HHmmss.fffffff");}}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the name of the user. </summary>
		///
		/// <value>	The name of the user. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		private static string UserName{get{return HttpContext.Current.User.Identity.Name;}}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the page session identifier header. </summary>
		///
		/// <value>	The page session identifier header. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string PageSessionIDHeader{
			get{
				string s = HttpContext.Current.Request.Headers[TIME_TAG_NAME];
				return s == null ? "" : s;
			}
	}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the identifier of the page session. </summary>
		///
		/// <value>	The identifier of the page session. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string PageSessionID{
			get{
				string s = PageSessionIDHeader;
				return s == "" ? TimeTag : s;
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the name of the page session. </summary>
		///
		/// <value>	The name of the page session. </value>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string PageSessionName{
			get{
				return HttpContext.Current.Request.UrlReferrer == null ? HttpContext.Current.Request.Url.PathAndQuery : HttpContext.Current.Request.UrlReferrer.PathAndQuery;
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Get ConcurrencyTag object from Cache. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	. </param>
		///
		/// <returns>	ConcurrencyTag object. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		internal static ConcurrencyTag GetTag(int applID){
			object tag = HttpContext.Current.Cache[applID.ToString()];
			return tag == null ? null : (ConcurrencyTag)tag;
		}
		//http://www.odddpost.com

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Query if 'applID' is locked. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	. </param>
		///
		/// <returns>	true if locked, false if not. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static bool IsLocked(int applID) { return GetTag(applID) != null; }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Query if 'applID' is locked by other. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	. </param>
		///
		/// <returns>	true if locked by other, false if not. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static bool IsLockedByOther(int applID) {
			ConcurrencyTag ct = GetTag(applID);
			return ct != null && ct.UserName != UserName; 
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Lock ApplID if possible. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">	. </param>
		///
		/// <returns>	PageSessionID - used on a client side. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string LockApplicant(int applID){
			return LockApplicant(applID,ConfigApp.ApplLockTimeOut);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Lock ApplID if possible. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <param name="applID">		. </param>
		/// <param name="seconds">	The seconds. </param>
		///
		/// <returns>	PageSessionID - used on a client side. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string LockApplicant(int applID,int seconds){
			return LockApplicant(applID,new TimeSpan(0,0,seconds));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Lock ApplID if possible. </summary>
		///
		/// <remarks>	Dimon, 11/24/2010. </remarks>
		///
		/// <exception cref="ConcurrencyException">	Thrown when concurrency. </exception>
		/// <exception cref="ApplicationException">	Thrown when application. </exception>
		///
		/// <param name="applID">			. </param>
		/// <param name="expiration">	. </param>
		///
		/// <returns>	PageSessionID - used on a client side. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static string LockApplicant(int applID,TimeSpan expiration){
			if( !ConfigApp.DoLock )return "NoLock";
			string sApplID = applID+"";
			string pageSessionID = PageSessionID;
			ConcurrencyTag concTag = GetTag(applID);
			if( concTag != null ){
				if( concTag.UserName != UserName && pageSessionID != concTag.PageSessionID && concTag.TimeTag.CompareTo(pageSessionID) > 0 )
					throw new ConcurrencyException(applID);
			}else concTag = new ConcurrencyTag();
			concTag.PageSessionID = pageSessionID;
			concTag.TimeTag = TimeTag;
			concTag.UserName = UserName;
			concTag.PageName = PageSessionName;
			concTag.Expiration = DateTime.Now.Add(expiration);

			HttpContext.Current.Cache.Insert(
				applID.ToString(),
				concTag,
				null,
				concTag.Expiration,
				TimeSpan.Zero,
				CacheItemPriority.NotRemovable,
				null);
			if( HttpContext.Current.Cache[applID+""] == null)
				HttpContext.Current.Cache.Add(
					applID.ToString(),
					concTag,
					null,
					concTag.Expiration,
					TimeSpan.Zero,
					CacheItemPriority.NotRemovable,
					null);
			if(HttpContext.Current.Cache[applID + ""] == null) throw new ApplicationException("Appl:" + applID + " did not make it to cache.");
			//return concTag.TimeTag;
			return pageSessionID;
		}

		// public static bool IsManagedMenu(int menuID){
		// 	return (bool)SQLHelperEx.ExecuteScalarEx(new UserObject().db,"SELECT IsManaged FROM v_Menu
		// 	WHERE MenuID=@MenuID",menuID);
		// }
		// public static TimeSpan GetApplLockTimeOut(int menuID){
		// 	object timeOut = SQLHelperEx.ExecuteScalarEx(new UserObject().db,"SELECT ApplLockTimeOut
		// 	FROM v_Menu WHERE MenuID=@MenuID",menuID);
		// 	return timeOut==System.DBNull.Value ? ConfigApp.ApplLockTimeOut : new TimeSpan(0,0,
		// 	(int)timeOut);
		// } 
	}
}
